﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Collections;
using System.ComponentModel;
using Ricciolo.StylesExplorer.Model;

namespace Ricciolo.StylesExplorer.Controls
{
    public class ExplorerTreeView : TreeView
    {
        private List<TreeNode> history = new List<TreeNode>();
        private int _historyIndex = -1;
        private bool skipHistory = false;

        private List<TreeNodeMap> _maps;
        private Hashtable flatNodes = new Hashtable();
        private IBindingList _dataSource;

        protected override void OnAfterSelect(TreeViewEventArgs e)
        {
            if (!skipHistory && e.Node != null)
            {
                history.Add(e.Node);
                this.HistoryIndex = history.Count - 1;
            }

            base.OnAfterSelect(e);
        }

        protected override void OnBeforeExpand(TreeViewCancelEventArgs e)
        {
            base.OnBeforeExpand(e);

            // Dynamic loading
            if (e.Node is ExplorerTreeNode && ((ExplorerTreeNode)e.Node).MustLoad && e.Node is ITreeNodeChildren)
            {
                ITreeNodeChildren tn = (ITreeNodeChildren)e.Node;
                CustomThreadPool.Default.QueueUserWorkItem(delegate
                {
                    IBindingList list = tn.Children;
                    flatNodes[list] = e.Node;
                    // Handle events
                    list.ListChanged += DataSource_ListChanged;
                    // Mark as loaded
                    ((ExplorerTreeNode)e.Node).MustLoad = false;
                    // Populante on main thread
                    this.BeginInvoke(new MethodInvoker(delegate
                    {
                        PopulateAll(list, e.Node.Nodes);
                    }), null);
                }, null);
            }
        }

        public IBindingList DataSource
        {
            get { return _dataSource; }
            set
            {
                SetData(value);
            }
        }

        public List<TreeNodeMap> Maps
        {
            get
            {
                if (_maps == null)
                    _maps = new List<TreeNodeMap>();

                return _maps;
            }
        }

        private void SetData(IBindingList value)
        {
            if (_dataSource != null)
                _dataSource.ListChanged -= new ListChangedEventHandler(DataSource_ListChanged);
            this.Nodes.Clear();
            _dataSource = value;

            if (value == null) return;

            value.ListChanged += new ListChangedEventHandler(DataSource_ListChanged);

            PopulateAll(value, this.Nodes);
        }

        private void PopulateAll(IBindingList list, TreeNodeCollection nodesList)
        {
            this.BeginUpdate();
            nodesList.Clear();
            foreach (object item in list)
            {
                AddItem(item, nodesList);
            }
            this.EndUpdate();
        }

        private void AddItem(object item, TreeNodeCollection list)
        {
            TreeNodeMap map = this.Maps.Find(tm => tm.DataType.IsAssignableFrom(item.GetType()));
            if (map != null)
            {
                TreeNode node = (TreeNode)Activator.CreateInstance(map.TreeNodeType, item);
                list.Add(node);

                // ExplorerTreeNode uses fake childnodes for dynamic load
                if (node is ITreeNodeChildren)
                {
                    if (!(node is ExplorerTreeNode) || !((ExplorerTreeNode)node).MustLoad)
                    {
                        flatNodes[((ITreeNodeChildren)node).Children] = node;
                        ((ITreeNodeChildren)node).Children.ListChanged += DataSource_ListChanged;
                        PopulateAll(((ITreeNodeChildren)node).Children, node.Nodes);
                    }
                }
                else
                    flatNodes[item] = node;

            }
        }

        void DataSource_ListChanged(object sender, ListChangedEventArgs e)
        {
            switch (e.ListChangedType)
            {
                case ListChangedType.Reset:
                    TreeNode node3 = (TreeNode)this.flatNodes[sender];
                    PopulateAll((IBindingList)sender, (node3 != null) ? node3.Nodes : this.Nodes);
                    break;
                case ListChangedType.ItemAdded:
                    object item = ((IList)sender)[e.NewIndex];
                    TreeNode node = (TreeNode)this.flatNodes[sender];
                    AddItem(item, (node != null) ? node.Parent.Nodes : this.Nodes);
                    break;
                case ListChangedType.ItemDeleted:
                    // Remove from flat list
                    //TreeNode node2 = (TreeNode)this.flatNodes[sender];
                    //if (node2 != null)
                    //{
                    //    // Remove from parent
                    //    node2.Remove();
                    //    this.flatNodes.Remove(sender);
                    //}
                    break;
            }
        }

        /// <summary>
        /// Prosegue con la navigazione
        /// </summary>
        public void GoForward()
        {
            if (this.HistoryIndex < this.history.Count)
            {
                this.HistoryIndex++;
                ChangeSelectionInternal(this.history[this.HistoryIndex]);
            }
        }

        /// <summary>
        /// Torna indietro con la navigazione
        /// </summary>
        public void GoBack()
        {
            if (this.HistoryIndex > 0)
            {
                this.HistoryIndex--;
                ChangeSelectionInternal(this.history[this.HistoryIndex]);
            }
        }

        /// <summary>
        /// Restistuisce se è possibile andare avanti con la navigazione
        /// </summary>
        public bool CanForward
        {
            get { return this.HistoryIndex < this.history.Count - 1; }
        }

        /// <summary>
        /// Restistuisce se è possibile tornare indietro con la navigazione
        /// </summary>
        public bool CanBack
        {
            get { return this.HistoryIndex > 0; }
        }

        private void ChangeSelectionInternal(TreeNode node)
        {
            try
            {
                skipHistory = true;

                this.SelectedNode = node;
            }
            finally
            {
                skipHistory = false;
            }
        }

        private int HistoryIndex
        {
            get { return _historyIndex; }
            set
            {
                _historyIndex = value;
            }
        }
    }

    public class TreeNodeMap
    {
        public Type TreeNodeType { get; set; }
        public Type DataType { get; set; }
    }

    public interface ITreeNodeChildren
    {
        IBindingList Children { get; }
    }
}
